/* ************************************************************************ */
/*                                                                          */
/*  haXe Media                                                              */
/*  Copyright (c)2007-2008 Russell Weir                                     */
/*                                                                          */
/* This library is free software; you can redistribute it and/or            */
/* modify it under the terms of the GNU Lesser General Public               */
/* License as published by the Free Software Foundation; either             */
/* version 2.1 of the License, or (at your option) any later version.       */
/*                                                                          */
/* This library is distributed in the hope that it will be useful,          */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        */
/* Lesser General Public License or the LICENSE file for more details.      */
/*                                                                          */
/* ************************************************************************ */

package hxmedia;
import format.Amf;
import format.Rtmp;
import format.So;
import hxmedia.Client;

class SharedObject {
	public var name(default,null) : String;
	public var persistent(default,null) : Bool;
	public var created(default,null) : Date;
	public var modified(default,null) : Bool;
	public var lastModified(default,null) : Date;

	var listeners : List<Client>;
	var data : Hash<AmfValue>;
	var dataCount : Int;
	var version : Int;
	var lock : neko.vm.Lock;
	var fo : neko.io.FileOutput;

	public function new(name, persistent) {
		lock = new neko.vm.Lock();
		this.name = name;
		this.persistent = persistent;
		this.version = 1; // TODO
		created = Date.now();
		modified = false;
		lastModified = Date.now();
		listeners = new List();
		initialize();
		lock.release();
	}

	/**
		Recreate the data storage. This is called when a non-persistent
		object has no subscribers left.
	**/
	function initialize() {
		data = new Hash();
		dataCount = 0;
	}

	/**
		Create an RTMP packet from the specified list of SOCommand's.
		Any call to this method must be wrapped in a lock.
	**/
	function makePacket(cmds:List<SOCommand>) {
		if(cmds == null || cmds.length == 0)
			throw "null or empty SOCommand list";
		//var pkt =
		return PShared({
			name: name,
			version: version,
			persist: persistent,
			unknown: 0,
			commands: cmds
		});
		//trace(pkt);
		//return pkt;
	}

	/**
		Check if object has the specified attribute
	**/
	public function hasAttribute(key) {
		return data.exists(key);
	}

	/**
		Returns the AMF encoded attribute specified by key
	**/
	public function getAttribute(key) {
		return data.get(key);
	}

	/**
		Client setting attibute key to value. The serial number
		is the version of the object the client has.
	**/
	public function setAttribute(c:Client,key:String, value:AmfValue, serial:Int) {
		var cmds = new List<SOCommand>();
		lock.wait();
		if(serial != version) {
			lock.release();
			cmds.add(SOStatus("SharedObject.VersionMismatch", "error"));
			c.addSharedObjectEvent(makePacket(cmds));
			return;
		}
		version++;
		lastModified = Date.now();

		cmds.add(SOUpdateAttribute(key));
		c.addSharedObjectEvent(makePacket(cmds));
		cmds = new List();
		var h = new Hash<AmfValue>();
		h.set(key,value);
		cmds.add(SOUpdateData(h));
		broadcast(makePacket(cmds),c);
		lock.release();
	}

	/**
		Remove one attribute from the object
		TODO: client receives what response?
	*/
	public function removeAttribute(name) {
		var cmds = new List<SOCommand>();
		lock.wait();
		cmds.add(SODeleteAttribute(name));
		data.remove(name);
		dataCount--;
		broadcast(makePacket(cmds));
		lock.release();
	}

	/**
		Remove one attribute from the object
	*/
	public function deleteAttributeData(name) {
		var cmds = new List<SOCommand>();
		lock.wait();
		cmds.add(SODeleteData(name));
		if(data.exists(name))
			data.set(name,null);
		//dataCount--;
		broadcast(makePacket(cmds));
		lock.release();
	}

	/**
		Send changes out to all clients. If ignore is not null
		that client will not be sent the broadcast message.
	**/
	function broadcast(p:RtmpPacket, ?ignore:Client) {
		for(i in listeners) {
			if(i != ignore)
				i.addSharedObjectEvent(p);
		}
	}

	/**
		Subscribe Client to this object, which occurs when the
		client calls connect()
	**/
	public function addListener(c : Client, persist:Bool) {
		var cmds = new List<SOCommand>();
		if(this.persistent != persist) {
			cmds.add(So.errorStatus(SOErrPersist));
			c.addSharedObjectEvent(makePacket(cmds));
			return;
		}

		lock.wait();
		//PShared( data : SOData );
		listeners.remove(c);
		listeners.add(c);
		//cmds.add(SOConnect);
		cmds.add(SOInitialData);
		if(!persistent)
			cmds.add(SOClearData);
		if(dataCount > 0) {
			var copy = new Hash<AmfValue>();
			for(k in data.keys()) {
				copy.set(k, data.get(k));
			}
			cmds.add(SOUpdateData(copy));
		}
		try {
			c.addSharedObjectEvent(makePacket(cmds));
		}
		catch(e:Dynamic) {
			trace(e);
		}
		lock.release();
	}

	/**
		Stop sending update events to specified Client.
	**/
	public function removeListener(c : Client) {
		//trace(here.methodName);
		lock.wait();
		listeners.remove(c);
		if(listeners.length == 0 && !persistent) {
			initialize();
		}
		lock.release();
	}

	/**
		Current number of subscribers
	**/
	public function listenerCount() : Int {
		return listeners.length;
	}

	/**
		Returns data encoded for AMF tranmission. Since the
		data hash already stores AMF values, the format.AMF.encode()
		function should not be used.
	*/
	function dataEncoded() {
		return AObject(data);
	}
}